/* snooper.c
 *
 * Copyright (c) 2000 Sean Walton and Macmillan Publishers.  Use may be in
 * whole or in part in accordance to the General Public License (GPL).
 *
 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
*/

/*****************************************************************************/
/*** snooper.c                                                             ***/
/***                                                                       ***/
/*** This program captures *all* packets that the network interface sees.  ***/
/*** Be very careful with this tool, because you may see all lot of info.  ***/
/*** Also, it uses the deprecated SOCK_PACKET socket type.  The newer and  ***/
/*** preferred method is with PF_PACKET.                                   ***/
/*****************************************************************************/

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <resolv.h>
#include <arpa/inet.h>
#include <errno.h>
#include <sys/types.h>
#include <linux/if_ether.h>

#define IP_SIZE        4
#define ETH_SIZE    6

typedef enum {
    eETH_ADDR, eIP_ADDR
} EAddress;

typedef unsigned char uchar;

/*--------------------------------------------------------------------*/
/* Ethernet Frame                                                     */
/*                                                                    */
/* This structure defines the fields within the ethernet frame. Since */
/* this programs gets the lowest-level packet, fragmented packets are */
/* not reassembled.  The first few fields contain the MAC addresses   */
/* of the source and destination. Note that this structure is set for */
/* little-endian format.                                              */
/*--------------------------------------------------------------------*/
struct ip_packet {
    struct {
        uchar dst_eth[ETH_SIZE];
        uchar src_eth[ETH_SIZE];
        uchar __unknwn[2];
    } hw_header;             /* hardware header */
    uint header_len:4;       /* header length in words in 32bit words */
    uint version:4;          /* 4-bit version */
    uint serve_type:8;       /* how to service packet */
    uint packet_len:16;      /* total size of packet in bytes */
    uint ID:16;              /* fragment ID */
    uint frag_offset:13;     /* to help reassembly */
    uint more_frags:1;       /* flag for "more frags to follow" */
    uint dont_frag:1;        /* flag to permit fragmentation */
    uint __reserved:1;       /* always zero */
    uint time_to_live:8;     /* maximum router hop count */
    uint protocol:8;         /* ICMP, UDP, TCP */
    uint hdr_chksum:16;      /* ones-comp. checksum of header */
    uchar IPv4_src[IP_SIZE]; /* IP address of originator */
    uchar IPv4_dst[IP_SIZE]; /* IP address of destination */
    uchar options[0];        /* up to 40 bytes */
    uchar data[0];           /* message data up to 64KB */
};

/*--------------------------------------------------------------------*/
/* dump                                                               */
/*                                                                    */
/* Dump a block of data in hex & ascii.                               */
/*--------------------------------------------------------------------*/
void dump(void *b, int len) {
    unsigned char *buf = b;
    int i, cnt = 0;
    char str[17];
    memset(str, 0, 17);
    for (i = 0; i < len; i++) {
        if (cnt % 16 == 0) {
            printf("  %s\nX: ", str, cnt);
            memset(str, 0, 17);
        }
        if (buf[cnt] < ' ' || buf[cnt] >= 127)
            str[cnt] = '.';
        else
            str[cnt] = buf[cnt];
        printf("X ", buf[cnt++]);
    }
    printf("  %*s\n\n", 16 + (16 - len) * 2, str);
}

/*--------------------------------------------------------------------*/
/* PrintAddr                                                          */
/*                                                                    */
/* Print the different types of address (MAC or IP).                  */
/*--------------------------------------------------------------------*/
void PrintAddr(char *msg, uchar *addr, EAddress is_ip) {
    int i;
    static struct {
        int len;
        char *fmt;
        char delim;
    } addr_fmt[] = {{ETH_SIZE, "%x", ':'},
                    {IP_SIZE,  "%d", '.'}};

    printf("%s", msg);
    for (i = 0; i < addr_fmt[is_ip].len; i++) {
        printf(addr_fmt[is_ip].fmt, addr[i]);
        if (i < addr_fmt[is_ip].len - 1)
            putchar(addr_fmt[is_ip].delim);
    }
}

/*--------------------------------------------------------------------*/
/* GetProtocol                                                        */
/*                                                                    */
/* Convert the protocol value into the alphabetic representation.     */
/*--------------------------------------------------------------------*/
char *GetProtocol(int value) {
    switch (value) {
        case IPPROTO_IP:
            return "IP";
        case IPPROTO_ICMP:
            return "ICMP";
        case IPPROTO_IGMP:
            return "IGMP";
        case IPPROTO_IPIP:
            return "IPIP";
        case IPPROTO_TCP:
            return "TCP";
        case IPPROTO_EGP:
            return "EGP";
        case IPPROTO_PUP:
            return "PUP";
        case IPPROTO_UDP:
            return "UDP";
        case IPPROTO_IDP:
            return "IDP";
        case IPPROTO_RSVP:
            return "RSVP";
        case IPPROTO_GRE:
            return "GRE";
        case IPPROTO_IPV6:
            return "IPV6/4";
        case IPPROTO_PIM:
            return "PIM";
        case IPPROTO_RAW:
            return "RAW";
        default:
            return "???";
    }
}

/*--------------------------------------------------------------------*/
/* DumpPacket                                                         */
/*                                                                    */
/* Display the read packet with data and fields.                      */
/*--------------------------------------------------------------------*/
void DumpPacket(char *buffer, int len) {
    struct ip_packet *ip = (void *) buffer;

    printf("-------------------------------------------------\n");
    dump(buffer, len);
    PrintAddr("Destination EtherID=", ip->hw_header.dst_eth, eETH_ADDR);
    PrintAddr(", Source EtherID=", ip->hw_header.src_eth, eETH_ADDR);
    printf("\nIPv%d: header-len=%d, type=%d, packet-size=%d, ID=%d\n",
           ip->version, ip->header_len * 4, ip->serve_type,
           ntohs(ip->packet_len), ntohs(ip->ID));
    printf("frag=%c, more=%c, offset=%d, TTL=%d, protocol=%s\n",
           (ip->dont_frag ? 'N' : 'Y'),
           (ip->more_frags ? 'N' : 'Y'),
           ip->frag_offset,
           ip->time_to_live, GetProtocol(ip->protocol));
    printf("checksum=%d, ", ntohs(ip->hdr_chksum));
    PrintAddr("source=", ip->IPv4_src, eIP_ADDR);
    PrintAddr(", destination=", ip->IPv4_dst, eIP_ADDR);
    printf("\n");
    fflush(stdout);
}

void PANIC(char *msg);

#define PANIC(msg)    {perror(msg);exit(0);}

/*--------------------------------------------------------------------*/
/* main                                                               */
/*                                                                    */
/* Open socket.  Repeatedly read and display records.                 */
/*--------------------------------------------------------------------*/
int main() {
    int sd, bytes_read;
    char data[1024];

    sd = socket(PF_INET, SOCK_PACKET, htons(ETH_P_ALL));
    if (sd < 0) PANIC("Snooper socket");
    do {
        bytes_read = recvfrom(sd, data, sizeof(data), 0, 0, 0);
        if (bytes_read > 0)
            DumpPacket(data, bytes_read);
    } while (bytes_read > 0);
    return 0;
}

